我們上回完成了CreateModal,接下來要完成的是Story,構成Story有兩個主要的部分:最上面的進度條、下面隨著進度條變換的圖片,以下內容會圍繞在這兩個地方,其餘不重要的部分就會省略。
在Components資料夾底下,新增StoryComponents資料夾。
在StoryComponents資料夾底下,新增Progressbar.jsx、Progressbar.css、StoryViewer.jsx。
首先編寫Progressbar.jsx,設定進度條的進度會隨著時間而變化。
import React, { useEffect, useState } from "react";
import "./Progressbar.css";
const Progressbar = ({index, activeIndex, duration}) => {
const [progress, setProgress] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setProgress((preProgress) => {
if (preProgress < 100) {
return preProgress + 1;
}
clearInterval(interval);
return preProgress;
});
}, duration / 100);
return () => {
clearInterval(interval);
};
}, [duration, activeIndex]);
useEffect(() => {
setProgress(0);
}, [activeIndex]);
const isActive = index === activeIndex;
return (
<div>
<div
className={`${isActive ? "progress-bar" : ""}`}
style={{ width: `${progress}%` }}
></div>
</div>
);
};
export default Progressbar;
在編寫StoryViewer.jsx前,先安裝會等下使用到的函式庫。
npm i styled-components
安裝完成後,開始將以下的內容寫入StoryViewer.jsx,圖片會不斷的每兩千毫秒變換,如果顯示到最後一張圖片,就回到第一張圖開始重複顯示。
import React, { useEffect, useState } from "react";
import { styled } from "styled-components";
const StoryViewerContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: black;
`;
const StoryImage = styled.img`
max-height: 90vh;
object-fit: contain;
`;
const StoryViewer = ({ stories }) => {
const [currentStoryIndex, setCurrentStoryIndex] = useState(0);
const [activeIndex, setActiveIndex] = useState(0);
const handleNextStory = () => {
if (currentStoryIndex < stories.length - 1) {
setCurrentStoryIndex(currentStoryIndex + 1);
setActiveIndex(activeIndex + 1);
} else if (currentStoryIndex === stories.length - 1) {
setCurrentStoryIndex(0);
setActiveIndex(0);
}
};
useEffect(() => {
const interval = setInterval(() => {
handleNextStory();
}, 2000);
return () => clearInterval(interval);
}, [currentStoryIndex]);
return (
<div>
<StoryViewerContainer>
<StoryImage src={stories?.[currentStoryIndex].image} />
</StoryViewerContainer>
</div>
);
};
export default StoryViewer;
在Pages資料夾底下,新增Story資料夾。
在Story資料夾底下,新增Story.jsx。
在Story.jsx中,先顯示我們的StoryViewer,至於Progressbar.jsx的內容以後處理。
在這裏將要顯示的圖片存放於stories中。
import React from "react";
import StoryViewer from "../../Components/StoryComponents/StoryViewer";
const Story = () => {
const stories = [
{
image:
"https://images.pexels.com/photos/15978352/pexels-photo-15978352.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
},
{
image:
"https://images.pexels.com/photos/14969818/pexels-photo-14969818.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
},
{
image:
"https://images.pexels.com/photos/15327222/pexels-photo-15327222.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
},
{
image:
"https://images.pexels.com/photos/18074917/pexels-photo-18074917.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
},
{
image:
"https://images.pexels.com/photos/8451450/pexels-photo-8451450.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
},
];
return (
<div>
<StoryViewer stories={stories} />
</div>
);
};
export default Story;
然後來到Router.jsx,添加story的路由。
import React from 'react'
import Sidebar from "../../Components/Sidebar/Sidebar"
import { Route, Routes } from "react-router-dom"
import HomePage from "../HomePage/HomePage"
import Profile from "../Profile/Profile"
import Story from "../Story/Story"
const Router = () => {
return (
<div>
<div className="flex">
<div className="w-[20%] border">
<Sidebar />
</div>
<div className="w-full">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/username" element={<Profile />} />
<Route path="/story" element={<Story />} />
</Routes>
</div>
</div>
</div>
)
}
export default Router
啟動專案後,到http://localhost:5173/story,就能看到不斷輪流顯示的圖片。
接下來,讓進度條顯示在畫面上,修改StoryViewer.jsx,傳入一些參數給Progressbar。
import React, { useEffect, useState } from "react";
import { styled } from "styled-components";
import Progressbar from "./Progressbar";
const StoryViewerContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: black;
`;
const StoryImage = styled.img`
max-height: 90vh;
object-fit: contain;
`;
const StoryViewer = ({ stories }) => {
const [currentStoryIndex, setCurrentStoryIndex] = useState(0);
const [activeIndex, setActiveIndex] = useState(0);
const handleNextStory = () => {
if (currentStoryIndex < stories.length - 1) {
setCurrentStoryIndex(currentStoryIndex + 1);
setActiveIndex(activeIndex + 1);
} else if (currentStoryIndex === stories.length - 1) {
setCurrentStoryIndex(0);
setActiveIndex(0);
}
};
useEffect(() => {
const interval = setInterval(() => {
handleNextStory();
}, 2000);
return () => clearInterval(interval);
}, [currentStoryIndex]);
return (
<div className="relative w-full">
<StoryViewerContainer>
<StoryImage src={stories?.[currentStoryIndex].image} />
<div className="absolute top-0 flex w-full">
{stories.map((item, index) => (
<Progressbar
key={index}
duration={2000}
index={index}
activeIndex={activeIndex}
/>
))}
</div>
</StoryViewerContainer>
</div>
);
};
export default StoryViewer;
到Progressbar.css,設定進度條的CSS。
.progress-bar-container{
width: 100%;
height: 4px;
background-color: gray;
opacity: .65;
border-radius: 4px;
overflow: hidden;
transition: opacity .3 ease-out;
margin: 1rem;
}
.progress-bar-container .active{
opacity: 1;
}
.progress-bar{
height: 100%;
background-color: white;
border-radius: 4px;
}
最後對Progressbar.jsx做一些小修改,導入CSS。
import React, { useEffect, useState } from "react";
import "./Progressbar.css";
const Progressbar = ({index, activeIndex, duration}) => {
const [progress, setProgress] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setProgress((preProgress) => {
if (preProgress < 100) {
return preProgress + 1;
}
clearInterval(interval);
return preProgress;
});
}, duration / 100);
return () => {
clearInterval(interval);
};
}, [duration, activeIndex]);
useEffect(() => {
setProgress(0);
}, [activeIndex]);
const isActive = index === activeIndex;
return (
<div className={`progress-bar-container ${isActive ? "active" : ""}`}>
<div
className={`${isActive ? "progress-bar" : ""}`}
style={{ width: `${progress}%` }}
></div>
</div>
);
};
export default Progressbar;
進度條完成後,我們的Story區塊就全部完成了。